/*
 *  Copyright 2011 Iurii Dunko, Iana Potochniak.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package org.opu.yyminijava.symboltable;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 
 * @author Iurii Dunko
 * @author Iana  Potochniak
 */
public class Scope {

    /**  */
    private int next = 0;
    /** Parent scope */
    private Scope parent;
    
    /**
     * If current scope can't find some record he look into this scop.
     * This value must be set only if current scope is class and this class has 
     * super class; In this case this value is representation of parent scope
     */
    private Scope defaultScope;
    /** List of children scopes */
    private List<Scope> children = new ArrayList<Scope>();
    /**
     * We can use this:<pre>
     * {@code private Map<String, Record> records; }.</pre>
     * <ol>Because in this case:
     *  <li>We can't have method and fields with the same name.</li>
     *  <li>We can't have method overloading.</li>
     * </ol>
     * And situation like this:
     * <pre>{@code
     * class Main {
     *    int methodName;
     *    int methodName(){}
     *    int methodName(int i){}
     * }
     * }</pre>
     * was been imposible. <br>
     * So we decided to use list that can have only unique value.
     * And each records must decide is it equals to another (Of Course with
     * type checking).
     */
    private UniqueArrayList records = new UniqueArrayList();

    /** The name of current scope (It's need only for print) */
    private String name = "";
    /** The name last added scope (It's need only for print) */
    private String lastAdded = "";

    public Scope(Scope parent, String name) {
        this.parent = parent;
        this.name = name;
    }

    public Scope(Scope parent) {
        this.parent = parent;
    }

    public Scope getParent() {
        return parent;
    }

    public void setParent(Scope parrent) {
        this.parent = parrent;
    }

    public Scope nextChild() {
        Scope nextC;

        if (next >= children.size()) {
            nextC = new Scope(this, lastAdded);
            children.add(nextC);
        } else {
            nextC = children.get(next);
        }

        next++;

        return nextC;

    }

    public Record<?> lookup(Record<?> key) {
        int indexOf = records.indexOf(key);
        if (indexOf != -1) {
			return records.get(indexOf);
        } else {
            if (defaultScope != null){
                return defaultScope.lookup(key);
            } else if (parent != null) {
                return parent.lookup(key);
            } else {
                return null;
            }
        }
    }

    public void resetScope() {
        next = 0;
        for (Scope scope : children) {
            scope.resetScope();
        }
    }

    public void put(Record<?> item) {
        records.add(item);
        lastAdded = item.getName();
    }

    Scope getDefaultScope() {
        // It's must be defined only be system table
        return defaultScope;
    }

    void setDefaultScope(Scope defaultScope) {
        this.defaultScope = defaultScope;
    }

    /**
     * Looked for a children scope with defined name
     * @param name name of children scope
     * @return children scope with specific name
     */
    Scope getChildrenWithName(String name){
        for (int i = 0; i < children.size(); i++) {
            if (children.get(i).getName().equals(name)){
                return children.get(i);
            }
        }
        return null;
    }

    public String getName() {
        return name;
    }

    public void print() {
        print(System.out);
    }

    public void print(PrintStream out) {
        print(out, "");
    }

    public void print(PrintStream out, String space) {
        out.println(space + name + " {");
        
        out.println(space + "    records:");
        for (int i = 0; i < records.size(); i++) {
            out.println(space + "        " + records.get(i));
        }
        
        if (!children.isEmpty()) {
            out.println(space + "    childrens:");
            for (int i = 0; i < children.size(); i++) {
                children.get(i).print(out, space + "        ");
            }
        }

        out.println(space + "}");
    }

    static class UniqueArrayList extends ArrayList<Record<?>> {

        private static final long serialVersionUID = 5960243137600449390L;

		@Override
        public boolean add(Record<?> e) {
            // TODO: Create exception structure, don't use java exception
            // Using error is temporary solution, we use exception when the
            // incoming file contains errors, when the problem in our compiler
            // we use errors (but it for now)
            if(contains(e)){
                throw new IllegalArgumentException("Element '" + e + "'already in list ");
            }

            if(e == null){
                throw new NullPointerException();
            }

            return super.add(e);
        }
    }
}
